
/**
\file
Definition of the global lua state with various helper functions.
\author Sean Tromans
*/

#include <sun_pre_compiled.hpp>

#if !defined(SUN_DISABLE_LUA)

#include <sun/lua/lua.hpp>

#include <sun/lua/needs_more_ninja/needs_more_ninja.hpp>

#include <sun/application/application.hpp>
#include <sun/lua/input/input.hpp>
#include <sun/lua/signals/signals.hpp>
#include <sun/window/window.hpp>
#include <sun/time/time.hpp>
#include <sun/game_state/game_state.hpp>

#include <rainbow/viewport/viewport_stack.hpp>
#include <rainbow/viewport/viewport.hpp>
#include <rainbow/viewport/pass/pass.hpp>
#include <rainbow/draw_command/draw_command.hpp>
#include <rainbow/graphics_device/graphics_device.hpp>

namespace sun
{

	namespace lua
	{

		namespace detail
		{
			/** Outputs any lua errors to the log file and debug panel. */
			void echo_error( )
			{
				std::string const error_message( lua_tostring( g_state, -1 ) );
				lua_pop( g_state, 1 );

				//	nmn::log error error_message
				//	echo output_message
				log( error_message );
			}

			/** Callback from Lua whenever the user prints a message. Outputs to log and console. */
			int print( lua_State * L )
			{
				int number_of_args = lua_gettop( L );
				if( number_of_args != -1 )
				{
					lua_getglobal( L, "tostring" );
					std::string output;
	    
					for( int i = 1; i <= number_of_args; ++i )
					{
						lua_pushvalue( L, -1 );
						lua_pushvalue( L, i );
						lua_call( L, 1, 1 );

						const char * const arg = lua_tostring( L, -1 );
						if( arg == nullptr )
						{
							return luaL_error( L, LUA_QL( "tostring" ) " must return a string to ", LUA_QL( "print" ) );
						}

						output.append( arg );

						lua_pop( L, 1 );
					};

					//	nmn::log output
					//	echo output
					log( output );
				}

				return 0;
			}

			/** Error callback from Lua to add filenames and line numbers. */
			int pcall_error( lua_State * L )
			{
				lua_Debug debug;
				lua_getstack( L, 1, &debug );
				lua_getinfo( L, "Sln", &debug );

				std::string const error_message( lua_tostring( L, -1 ) );
				lua_pop( L, 1 );

				std::string output_message = nmn::create_string( "%1% : %2% ", debug.short_src, debug.currentline );

				if ( debug.name != nullptr )
				{
					output_message += nmn::create_string( "( %1% '%2%' ) ", debug.namewhat, debug.name );
				}

				output_message += error_message;
				lua_pushstring(L, output_message.c_str());

				//	nmn::log error output_message
				//	echo output_message
				log( output_message );

				return 1;
			}

		};

		/** Globally accessible lua_State. Probably won't ever get used in this form. */
		lua_State * g_state = nullptr;

		/**
		Creates the lua_State and initialises the engines interface.
		@return false if an error occured during initialisation.
		\throws already_initialised
		\author Sean Tromans
		*/
		bool initialise_state( )
		{
			if ( g_state != nullptr )
			{
				throw already_initialised( __FUNCTION__ );
			}

			g_state = luaL_newstate( );
			if ( g_state == nullptr )
			{
				return false;
			}

			luaL_openlibs( g_state );
			luabind::open( g_state );

			lua_register( g_state, "print", &detail::print );
			luabind::set_pcall_callback( &detail::pcall_error );

			//	Initialise the lua interface to the engine.
			nmn_lua_interface( g_state );

			application::lua_interface( g_state );
			input_lua_interface( g_state );
			signals_lua_interface( g_state );
			window::lua_interface( g_state );
			time::lua_interface( g_state );
			game_state::lua_interface( g_state );

			rainbow::viewport_stack::lua_interface( g_state );
			rainbow::viewport::lua_interface( g_state );
			rainbow::pass::lua_interface( g_state );
			rainbow::draw_information::lua_interface( g_state );
			rainbow::decorated_draw_information::lua_interface( g_state );
			
			rainbow::blend_state::lua_interface( g_state );
			rainbow::depth_state::lua_interface( g_state );
			rainbow::stencil_state::lua_interface( g_state );
			rainbow::culling_state::lua_interface( g_state );
			rainbow::graphics_device::lua_interface( g_state );

			return true;
		}

		/**
		Destroys the lua_State.
		\throws not_initialised
		\author Sean Tromans
		*/
		void destroy_state( )
		{
			if ( g_state == nullptr )
			{
				throw not_initialised( __FUNCTION__ );
			}

			lua_close( g_state );
		}

		/**
		Wraps luaL_dostring and handles all error output.
		@param str The string to run through lua.
		@return false if there was any error.
		\throws not_initialised
		\author Sean Tromans
		*/
		bool do_string( std::string const & str )
		{
			if ( g_state == nullptr )
			{
				throw not_initialised( __FUNCTION__ );
			}

			if ( luaL_dostring( g_state, str.c_str( ) ) != 0 )
			{
				detail::echo_error( );
				return false;
			}

			return true;
		}

		/**
		Wraps luaL_dofile and handles all error output.
		@param str The filename of the lua script to run.
		@return false if there was any error.
		\throws not_initialised
		\author Sean Tromans
		*/
		bool do_file( std::string const & str )
		{
			if ( g_state == nullptr )
			{
				throw not_initialised( __FUNCTION__ );
			}

			if ( luaL_dofile( g_state, str.c_str( ) ) != 0 )
			{
				detail::echo_error( );
				return false;
			}

			return true;
		}

	};

};

#endif